package com.google.gwt.livinglife.server;

import javax.jdo.Query;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.gwt.livinglife.client.AdditionalPostSelector;
import com.google.gwt.livinglife.client.ChallengeSelector;
import com.google.gwt.livinglife.client.GroupSelector;
import com.google.gwt.livinglife.client.LivingLifeService;
import com.google.gwt.livinglife.client.NotLoggedInException;
import com.google.gwt.livinglife.client.ObjectNotFoundException;
import com.google.gwt.livinglife.client.data.ChallengeData;
import com.google.gwt.livinglife.client.data.CommentData;
import com.google.gwt.livinglife.client.data.LivingLifeGroupData;
import com.google.gwt.livinglife.client.data.LivingLifeUserData;
import com.google.gwt.livinglife.client.data.ResponseData;
import com.google.gwt.livinglife.client.data.UserIdData;
import com.google.gwt.livinglife.server.data.BasicPostWithStats;
import com.google.gwt.livinglife.server.data.Challenge;
import com.google.gwt.livinglife.server.data.Comment;
import com.google.gwt.livinglife.server.data.LivingLifeGroup;
import com.google.gwt.livinglife.server.data.LivingLifeUser;
import com.google.gwt.livinglife.server.data.Response;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import javax.jdo.PersistenceManager;
import java.util.logging.Logger;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * This class is responsible for all interactions with the client
 * that require any read or write to the data store. This involves, creation and retrieval of 
 *  * Users
 *  * Groups
 *  * Challenges
 *  * Responses
 *  * Comments
 * @author amitprakash
 *
 */
public class LivingLifeServiceImpl extends RemoteServiceServlet implements
LivingLifeService {
	private static final Logger LOG = Logger.getLogger(LivingLifeServiceImpl.class.getName());

	private static int INITIAL_COMMENT_COUNT = 5;
	private static int INITIAL_RESPONSE_COUNT = 5;

	public Long addChallenge(ChallengeData challengeData) throws NotLoggedInException {
		checkLoggedIn(challengeData.getAuthor().getId());
		Challenge challenge = new Challenge(challengeData);
		PersistenceManager pm = getPersistenceManager();
		try {
			pm.makePersistent(challenge);
		} finally {
			pm.close();
		}
		return challenge.getId();
	}

	public Long addComment(CommentData comment_data) throws NotLoggedInException {
		checkLoggedIn(comment_data.getAuthor().getId());
		Comment comment = new Comment(comment_data);
		PersistenceManager pm = getPersistenceManager();
		pm.makePersistent(comment);
		pm.close();
		pm = getPersistenceManager();
		javax.jdo.Transaction tx = pm.currentTransaction();
		try {	
			tx.begin();	
			Response response = pm.getObjectById(Response.class, comment_data.getParent_response_id());
			response.getComments().add(comment.getId());
			pm.makePersistent(response);
			tx.commit();
		} finally {
			if (tx.isActive()) {
				tx.rollback();
				// Since we could not add the comment to response, Delete the orphan comment
				PersistenceManager pm1 = getPersistenceManager();
				pm1.deletePersistent(comment);
				pm1.close();
				return null;
			}
		}
		return comment.getId();
	}

	public Long addGroup(LivingLifeGroupData group_data, UserIdData founder)
	throws NotLoggedInException {
		checkLoggedIn(founder.getId());
		// Check if the group exists already
		if (groupExists(group_data.getName())) {
			return new Long(-1);
		}
		
		LivingLifeGroup group = new LivingLifeGroup(group_data);
		group.getMembers().add(founder.getId());
		group.getModerators().add(founder.getId());
		PersistenceManager pm = getPersistenceManager();
		try {
			pm.makePersistent(group);
			assert(group.getId() != null);
			LivingLifeUser u = pm.getObjectById(LivingLifeUser.class, founder.getId());
			u.getGroups().add(group.getId());
			pm.makePersistent(u);
		} finally {
			pm.close();
		}
		// add the group to the user data as well
		
		return group.getId();
		// TODO(amit): deal with the cases where we created the group but failed to 
		// add the group to user's list of groups. This would cause an orphan group
		// to be created.
	}
	@Override
	public void deleteGroup(Long groupId) throws NotLoggedInException {
		PersistenceManager pm = getPersistenceManager();
		try {
			LivingLifeGroup group = pm.getObjectById(LivingLifeGroup.class, groupId);
			Query q = pm.newQuery(Challenge.class);
			q.setFilter("group_id == group_param");
			q.declareParameters("Long group_param");
			List<Challenge> challenges = (List<Challenge>) q.execute(groupId);
			Iterator<Challenge> iter = challenges.iterator();
			while(iter.hasNext()) {
				pm.deletePersistent(iter.next());
			}
			pm.deletePersistent(group);
		} finally {
			pm.close();
		}
	}
	public Long addResponse(ResponseData response_data) throws NotLoggedInException {
		checkLoggedIn(response_data.getAuthor().getId());
		Response response = new Response(response_data);
		PersistenceManager pm = getPersistenceManager();
		Boolean success = false;
		try {
			pm.makePersistent(response);
			Challenge challenge = pm.getObjectById(Challenge.class, response_data.getChallenge());
			challenge.getResponses().add(response.getId());
			pm.makePersistent(challenge);
			success = true;
		} finally {
			pm.close();
			if (!success) {
				// Delete the response if we ended up persisting it.
				PersistenceManager pm1 = getPersistenceManager();
				try {
					pm1.deletePersistent(response);
				} finally {
					pm1.close();
				}
				return null;
			}
		}
		return response.getId();
	}

	public void updateUserData(LivingLifeUserData userData) throws NotLoggedInException {
		LOG.info("req for Writing user info " + userData.toString() );
		checkLoggedIn(userData.getId());
		LivingLifeUser user = new LivingLifeUser(userData);
		PersistenceManager pm = getPersistenceManager();
		try {
			pm.makePersistent(user);
		} finally {
			pm.close();
		}
		LOG.info("Wrote User info " + user.toString());
	}

	public void addUserToGroup(String userId, Long groupId)
	throws NotLoggedInException {
		checkLoggedIn(userId);
		PersistenceManager pm = getPersistenceManager();
		try {	
			LivingLifeUser user = pm.getObjectById(LivingLifeUser.class, userId);
			LivingLifeGroup group = pm.getObjectById(LivingLifeGroup.class, groupId);
			UserIdData user_id_data = DataObjectConverter.getUserIdData(user);
			group.getMembers().add(user_id_data.getId());
			pm.makePersistent(group);
			user.getGroups().add(group.getId());
			pm.makePersistent(user);
		} finally {
			pm.close();
		}
	}
	/**
	 * This method is called when the user requests to see more comments than
	 * fetched in the original request.
	 */
	public CommentData[] getAdditionalComments(AdditionalPostSelector selector) {
		List<CommentData> comments = retriveTopComments(selector.getPost_id(), selector.getOrder(),
				selector.getStart_index(), selector.getEnd_index());
		return (CommentData[]) comments.toArray();
	}

	public ResponseData[] getAdditionalResponses(AdditionalPostSelector selector) {
		List<ResponseData> responses = retriveTopResponses(selector.getPost_id(), selector.getOrder(),
				selector.getStart_index(), selector.getEnd_index());
		return (ResponseData[]) responses.toArray();
	}

	public ChallengeData[] getChallenges(ChallengeSelector selector) {
		PersistenceManager pm = getPersistenceManager();
		// leaving out date range for now to get started with less complex code.
		// TODO: add end date and start date requirements.
		Query q = pm.newQuery(Challenge.class);
		// Set appropriate filters for group_id and author_id.
		if (selector.getGroup_id() != null) {
			if (selector.getUser_id() != null) {
				q.setFilter("group_id == group_id_param && author_id == author_id_param");
				q.declareParameters("Long group_id_param, String author_id_param");
			} else {
				q.setFilter("group_id == group_id_param");
				q.declareParameters("Long group_id_param");
			}
		} else {
			if (selector.getUser_id() != null) {
				q.setFilter("author_id == author_id_param");
				q.declareParameters("String author_id_param");
			}
		}
		q.setRange(selector.getStart_index(), selector.getEnd_index());
		// Set ordering
		setQueryOrder(q, selector.getOrder());
		// Execute the actual query.
		List<Challenge> results;
		if (selector.getGroup_id() != null) {
			if (selector.getUser_id() != null) {
				results = (List<Challenge>) q.execute(selector.getGroup_id(), selector.getUser_id());
			} else {
				results = (List<Challenge>) q.execute(selector.getGroup_id());
			}
		} else {
			if (selector.getUser_id() != null) {
				results = (List<Challenge>) q.execute(selector.getUser_id());
			} else {
				results = (List<Challenge>) q.execute();
			}
		}
		ChallengeData[] challenges = new ChallengeData[results.size()];
		Iterator<Challenge> i = results.iterator();
		int index = 0;
		while(i.hasNext()) {
			Challenge c = i.next();
			ChallengeData challenge_data = DataObjectConverter.getChallengeData(c);
			fillUpResponses(challenge_data, c.getResponses(), INITIAL_RESPONSE_COUNT, selector.getOrder());
			challenges[index] = challenge_data;
			index++;
		}
		return challenges;
	}

	private void setQueryOrder(Query q, ChallengeSelector.SortOrder o) {
		switch(o) {
		case TIME: q.setOrdering("creation_date descending"); break;
		case LIKED: q.setOrdering("num_liked descending"); break;
		case INSPIRED: q.setOrdering("num_inspired descending"); break;
		case NUM_RESPONSES: q.setOrdering("num_responses descending"); break;
		}
	}	


	public int inappropriateStory(Long storyId)
	throws NotLoggedInException {
		int num_inappropriate = -1;
		PersistenceManager pm = getPersistenceManager();
		try {
			BasicPostWithStats bpw = pm.getObjectById(BasicPostWithStats.class, storyId);
			bpw.getFound_inappropriate().add(getUser());
			num_inappropriate = bpw.getFound_inappropriate().size();
		} finally {
		}
		return num_inappropriate;
	}

	public int inspiredBy(Long storyId) throws NotLoggedInException {
		int num_inspired = -1;
		PersistenceManager pm = getPersistenceManager();
		try {
			BasicPostWithStats bpw = pm.getObjectById(BasicPostWithStats.class, storyId);
			bpw.getInspired().add(getUser());
			num_inspired = bpw.getInspired().size();
		} finally {
		}
		return num_inspired;
	}

	public int likeStory(Long storyId) throws NotLoggedInException {
		int num_liked = -1;
		PersistenceManager pm = getPersistenceManager();
		try {
			BasicPostWithStats bpw = pm.getObjectById(BasicPostWithStats.class, storyId);
			bpw.getLiked().add(getUser());
			num_liked = bpw.getLiked().size();
		} finally {
		}
		return num_liked;
	}

	public void removeUserFromGroup(String userId, Long groupId)
	throws NotLoggedInException {
		checkLoggedIn(userId);
		PersistenceManager pm = getPersistenceManager();
		javax.jdo.Transaction tx = pm.currentTransaction();
		try {	
			tx.begin();
			LivingLifeUser user = pm.getObjectById(LivingLifeUser.class, userId);
			LivingLifeGroup group = pm.getObjectById(LivingLifeGroup.class, groupId);
			user.getGroups().remove(groupId);
			Iterator<String> user_iter = group.getMembers().iterator();
			while(user_iter.hasNext()) {
				if(user_iter.next().equals(userId)) {
					user_iter.remove();
					break;
				}
			}
			Iterator<Long> group_iter = user.getGroups().iterator();
			while(group_iter.hasNext()) {
				if(group_iter.next() == groupId) {
					group_iter.remove();
					break;
				}
			}
			pm.makePersistent(group);
			pm.makePersistent(user);
			tx.commit();
		} finally {
			if (tx.isActive()) {
				tx.rollback();
			}
		}
	}
	private void fillUpResponses(ChallengeData challenge_data, List<Long>responses, int num_responses_to_fill,
			ChallengeSelector.SortOrder o) {
		PersistenceManager pm = getPersistenceManager();
		if (num_responses_to_fill <= responses.size()) {
			// Lets pull all the responses from the data store
			Iterator<Long> iter = responses.iterator();
			while(iter.hasNext()) {
				Response r = pm.getObjectById(Response.class, iter.next());
				ResponseData rd =  DataObjectConverter.getResponseData(r);
				fillUpComments(rd, r.getComments(), INITIAL_COMMENT_COUNT, o);
				challenge_data.getResponses().add(rd);
			}
		} else {
			List<ResponseData> responseList = retriveTopResponses(challenge_data.getId(), o, 0, num_responses_to_fill);
			challenge_data.setResponses(responseList);
		}
	}

	private void fillUpComments(ResponseData response_data, List<Long>comments, int num_comments_to_fill,
			ChallengeSelector.SortOrder o) {
		PersistenceManager pm = getPersistenceManager();
		if (num_comments_to_fill <= comments.size()) {
			// Lets pull all the responses from the data store
			Iterator<Long> iter = comments.iterator();
			while(iter.hasNext()) {
				Comment c = pm.getObjectById(Comment.class, iter.next());
				response_data.getComments().add(DataObjectConverter.getCommentData(c));
			}
		} else {
			List<CommentData> commentList = retriveTopComments(response_data.getId(), o, 0, num_comments_to_fill);
			response_data.setComments(commentList);
		}
	}

	private List<ResponseData> retriveTopResponses(Long challengeId, ChallengeSelector.SortOrder o,
			int start_index, int end_index) {
		PersistenceManager pm = getPersistenceManager();
		Query q = pm.newQuery(Response.class);
		q.setFilter("challenge == parent_param");
		q.declareParameters("Long parent_param");
		setQueryOrder(q, o);
		q.setRange(start_index, end_index);
		List<Response> result = null;
		try {
			result = (List<Response>) q.execute(challengeId);
		} finally {
		}
		List<ResponseData> responses = new ArrayList<ResponseData>();
		if (result != null) {
			Iterator<Response> iter = result.iterator();
			while(iter.hasNext()) {
				Response r = iter.next();
				ResponseData rd = DataObjectConverter.getResponseData(r);
				fillUpComments(rd, r.getComments(), INITIAL_COMMENT_COUNT, o);
				responses.add(rd);
			}
		}
		return responses;
	}
	private List<CommentData> retriveTopComments(Long responseId, ChallengeSelector.SortOrder o,
			int start_index, int end_index) {
		PersistenceManager pm = getPersistenceManager();
		Query q = pm.newQuery(Comment.class);
		q.setFilter("parent_response_id == parent_param");
		q.declareParameters("Long parent_param");
		setQueryOrder(q, o);
		q.setRange(start_index, end_index);
		List<Comment> result = null;
		try {
			result = (List<Comment>) q.execute(responseId);
		} finally {
		}
		List<CommentData> comments = new ArrayList<CommentData>();
		if (result != null) {
			Iterator<Comment> iter = result.iterator();
			while(iter.hasNext()) {
				Comment c = iter.next();
				comments.add(DataObjectConverter.getCommentData(c));
			}
		}
		return comments;
	}

	/**
	 * Check that a given user is logged or not.
	 * @param user_id
	 * @throws NotLoggedInException
	 */
	private void checkLoggedIn(String user_id) throws NotLoggedInException {
		User user = getUser();
		if ( user == null) {
			throw new NotLoggedInException("Not logged in.");
		}
		LivingLifeUser ll_user;
		PersistenceManager pm = getPersistenceManager();
		try {
			ll_user = pm.getObjectById(LivingLifeUser.class, user_id);
		} finally {
			pm.close();
		}
		if (!ll_user.getGoogle_user().equals(user)) {
			throw new NotLoggedInException("Expected " + ll_user.getGoogle_user().getEmail() + " but found "
					+ user.getEmail() + " Logged in.");
		}
	}

	private User getUser() {
		UserService userService = UserServiceFactory.getUserService();
		return userService.getCurrentUser();
	}

	private PersistenceManager getPersistenceManager() {
		return PMF.get().getPersistenceManager();
	}
	
	private Boolean groupExists(String groupName) {
		PersistenceManager pm = getPersistenceManager();
		Query q = pm.newQuery(LivingLifeGroup.class);
		q.setFilter("name == name_param");
		q.declareParameters("String name_param");
		List<LivingLifeGroup> result = (List<LivingLifeGroup>) q.execute(groupName);
		if (result.size() > 0) {
			return true;
		}
		return false;
	}

	public ChallengeData getChallengeById(Long Challenge)  throws ObjectNotFoundException {
		PersistenceManager pm = getPersistenceManager();
		Challenge c;
		try {
			c = pm.getObjectById(Challenge.class, Challenge);
		} finally {
			pm.close();
		}
		if (c == null) {
			 throw new ObjectNotFoundException("ChallengID " + Challenge + " doesn't exist.");
		}
		ChallengeData cd = DataObjectConverter.getChallengeData(c);
		fillUpResponses(cd, c.getResponses(), INITIAL_RESPONSE_COUNT, ChallengeSelector.SortOrder.TIME);
		return cd;
	}

	public LivingLifeGroupData getGroupById(Long id)  throws ObjectNotFoundException{
		PersistenceManager pm = getPersistenceManager();
		LivingLifeGroup g;
		try {
			g = pm.getObjectById(LivingLifeGroup.class, id);
		} finally {
			pm.close();
		}
		if (g == null) {
			 throw new ObjectNotFoundException("Group ID " + id + " doesn't exist.");
		}
		return DataObjectConverter.getlLivingLifeGroupData(g);
	}

	public LivingLifeGroupData getGroupByName(String groupName)  throws ObjectNotFoundException{
		PersistenceManager pm = getPersistenceManager();
		Query q = pm.newQuery(LivingLifeGroup.class);
		q.setFilter("name == name_param");
		q.declareParameters("String name_param");
		q.setRange(0, 1);
		List<LivingLifeGroup> result = (List<LivingLifeGroup>) q.execute(groupName);
		if (result.size() == 0) {
			throw new ObjectNotFoundException("Group " + groupName + " doesn't exist.");
		}
		Iterator<LivingLifeGroup> iter = result.iterator();
		return DataObjectConverter.getlLivingLifeGroupData(iter.next());
	}
	
	@Override
	public LivingLifeGroupData[] getTopGroups(GroupSelector selector) {
		PersistenceManager pm = getPersistenceManager();
		Query q = pm.newQuery(LivingLifeGroup.class);
		q.setRange(selector.getStart_index(), selector.getEnd_index());
		List<LivingLifeGroup> result = (List<LivingLifeGroup>) q.execute();
		LivingLifeGroupData groups[] = new LivingLifeGroupData[result.size()];
		Iterator<LivingLifeGroup> iter = result.iterator();
		int index = 0;
		while(iter.hasNext()) {
			LivingLifeGroup group = iter.next();
			group.getMembers().clear();
			group.getModerators().clear();
			groups[index] = DataObjectConverter.getlLivingLifeGroupData(group);
			index++;
		}
		return groups;
	}

	public LivingLifeUserData[] getUserByEmail(String email) {
		PersistenceManager pm = getPersistenceManager();
		Query q = pm.newQuery(LivingLifeUser.class);
		q.setFilter("communication_email == email_param");
		q.declareParameters("String email_param");
		List<LivingLifeUser> result = (List<LivingLifeUser>) q.execute(email);
		ArrayList<LivingLifeUserData> userData = new ArrayList<LivingLifeUserData>();
		if (result != null) {
			Iterator<LivingLifeUser> iter = result.iterator();
			while(iter.hasNext()) {
				userData.add(DataObjectConverter.getLivingLifeUserData(iter.next()));
			}
		}
		return (LivingLifeUserData[]) userData.toArray(); 
	}

	@Override
	public LivingLifeUserData getUserById(String id)  throws ObjectNotFoundException{
		PersistenceManager pm = getPersistenceManager();
		LivingLifeUser u;
		try {
			u = pm.getObjectById(LivingLifeUser.class, id);
		} finally {
			pm.close();
		}
		if (u == null) {
			 throw new ObjectNotFoundException("User ID " + id + " doesn't exist.");
		}
		return DataObjectConverter.getLivingLifeUserData(u);
	}

	@Override
	public LivingLifeUserData[] getUserByNickname(String name){
		PersistenceManager pm = getPersistenceManager();
		Query q = pm.newQuery(LivingLifeUser.class);
		q.setFilter("nickname == name_param");
		q.declareParameters("String name_param");
		List<LivingLifeUser> result = (List<LivingLifeUser>) q.execute(name);
		ArrayList<LivingLifeUserData> userData = new ArrayList<LivingLifeUserData>();
		if (result != null) {
			Iterator<LivingLifeUser> iter = result.iterator();
			while(iter.hasNext()) {
				userData.add(DataObjectConverter.getLivingLifeUserData(iter.next()));
			}
		}
		return (LivingLifeUserData[]) userData.toArray(); 
	}
}